package db_gorm

import (
	"fmt"
	"gitee.com/lipore/plume/logger"
	"github.com/spf13/viper"
	"time"
)

type DBType int

const (
	Mysql DBType = iota + 1
	Postgres
	SQLite
)

type Config struct {
	ShowSql       bool
	SlowThreshold time.Duration
	Dsn           string
	DBType        DBType
}

func NewConfig(opts ...Options) *Config {
	config := &Config{}
	for _, opt := range opts {
		opt.Apply(config)
	}
	return config
}

type Options interface {
	Apply(c *Config)
}

type withShowSql struct {
	showSql bool
}

func (w *withShowSql) Apply(c *Config) {
	c.ShowSql = w.showSql
}

func WithShowSql(showSql bool) Options {
	return &withShowSql{showSql: showSql}
}

type withSlowThreshold struct {
	slowThreshold time.Duration
}

func (w *withSlowThreshold) Apply(c *Config) {
	c.SlowThreshold = w.slowThreshold
}

func WithSlowThreshold(slowThreshold time.Duration) Options {
	return &withSlowThreshold{slowThreshold: slowThreshold}
}

type withDsn struct {
	dsn string
}

func (w *withDsn) Apply(c *Config) {
	c.Dsn = w.dsn
}

// WithDsn, dsn example:
// - for postgres: host=localhost user=postgres password=123456 dbname=gorm port=5432 sslmode=disable TimeZone=Asia/Shanghai
// - for mysql: root:123456@tcp(127.0.0.1:3306)/gorm?charset=utf8mb4&parseTime=True&loc=Local
func WithDsn(dsn string) Options {
	return &withDsn{dsn: dsn}
}

type withDBType struct {
	dBType DBType
}

func (w *withDBType) Apply(c *Config) {
	c.DBType = w.dBType
}

func WithDBType(dBType DBType) Options {
	return &withDBType{dBType: dBType}
}

func ParseMysqlConfig(v *viper.Viper) []Options {
	if v == nil {
		return nil
	}
	host := v.GetString("host")
	if host == "" {
		host = "localhost"
		logger.Warn("host not set, fallback to 127.0.0.1")
	}
	port := v.GetInt("port")
	if port == 0 {
		port = 3306
		logger.Warnf("port not set, fallback to 3306")
	}
	user := v.GetString("user")
	if user == "" {
		user = "root"
		logger.Warnf("user not set, fallback to root")
	}
	password := v.GetString("password")
	if password == "" {
		logger.Warn("password not set")
	}
	database := v.GetString("database")
	if database == "" {
		database = "pigeon"
		logger.Warn("database not set, fallback to pigeon")
	}
	showSql := v.GetBool("showSql")

	return []Options{
		WithDBType(Mysql),
		WithDsn(fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=utf8mb4&parseTime=True&loc=Local", user, password, host, port, database)),
		WithShowSql(showSql),
		WithSlowThreshold(1 * time.Second),
	}
}

func ParsePostgresSqlConfig(v *viper.Viper) []Options {
	if v == nil {
		return nil
	}
	host := v.GetString("host")
	if host == "" {
		host = "localhost"
		logger.Warnf("host not set, fallback to %s", host)
	}
	port := v.GetInt("port")
	if port == 0 {
		port = 6379
		logger.Warnf("port not set, fallback to %d", port)
	}
	user := v.GetString("user")
	if user == "" {
		user = "root"
		logger.Warnf("user not set, fallback to %s", user)
	}
	password := v.GetString("password")
	if password == "" {
		logger.Warn("password not set")
	}
	database := v.GetString("database")
	if database == "" {
		database = "pigeon"
		logger.Warnf("database not set, fallback to %s", database)
	}
	showSql := v.GetBool("showSql")
	timeZone := v.GetString("timeZone")
	if timeZone == "" {
		timeZone = "Asia/Shanghai"
		logger.Warnf("time zone not set, fallback to %s", timeZone)
	}

	return []Options{
		WithDBType(Postgres),
		WithDsn(fmt.Sprintf("host=%s port=%d user=%s password=%s dbname=%s sslmode=disable TimeZone=%s", host, port, user, password, database, timeZone)),
		WithShowSql(showSql),
		WithSlowThreshold(1 * time.Second),
	}
}

func ParseSqliteOptions(v *viper.Viper) []Options {
	if v == nil {
		return nil
	}
	dsn := v.GetString("path")
	if dsn == "" {
		dsn = "pigeon.db"
		logger.Warnf("database file not set, fallback to %s", dsn)
	}
	showSql := v.GetBool("showSql")
	return []Options{
		WithDBType(SQLite),
		WithDsn(dsn),
		WithShowSql(showSql),
	}
}

func ParseOptions(v *viper.Viper) []Options {
	if v == nil {
		return nil
	}
	if c := v.Sub("psql"); c != nil {
		return ParsePostgresSqlConfig(c)
	}
	if c := v.Sub("mysql"); c != nil {
		return ParseMysqlConfig(c)
	}
	if c := v.Sub("sqlite"); c != nil {
		return ParseSqliteOptions(c)
	}
	return nil
}
